﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace GameDataTempshitEditor
{
    public partial class SaveAssignObjects : Form
    {
        public SaveAssignObjects()
        {
            InitializeComponent();

            treeView2.AfterExpand += treeView2_CacheTreeIcon;
            treeView2.AfterCollapse += treeView2_CacheTreeIcon;

            treeView1.ItemDrag += treeView1_ItemDrag;
            treeView2.ItemDrag += treeView2_ItemDrag;

            treeView2.AllowDrop = true;
            treeView2.DragDrop += treeView2_DragDrop;
            treeView2.DragEnter += treeView2_DragEnter;
            treeView2.DragLeave += treeView2_DragLeave;
            treeView2.DragOver += treeView2_DragOver;
        }

        void treeView2_CacheTreeIcon(object sender, TreeViewEventArgs e)
        {
            var dirNode = e.Node as TreeNode_Base;
            if ( dirNode != null )
                dirNode.UpdateIcon();
        }

        class ObjectBundleInfo
        {
        }

        class FileInfo
        {
            private readonly Red.GameData.Raw.DataFile _File;
            private readonly string _Name;
            private readonly DirectoryInfo _Parent;
            private readonly Boolean _IsNew;

            public Red.GameData.Raw.DataFile File { get { return _File; } }
            public string Name { get { return _Name; } }
            private DirectoryInfo Parent { get { return _Parent; } }
            public Boolean IsNew { get { return _IsNew; } }

            public FileInfo(Red.GameData.Raw.DataFile file, string name, DirectoryInfo parent, bool isNew)
            {
                _File = file;
                _Name = name;
                _Parent = parent;
                _IsNew = isNew;
            }
        }

        class DirectoryInfo
        {
            public DirectoryInfo( string absolutePath, string name, DirectoryInfo parent)
            {
                _Name = name;
                _AbsolutePath = absolutePath;
                _Parent = parent;
                _Dirs = new List<DirectoryInfo>();
                _Files = new List<FileInfo>();
            }

            public DirectoryInfo ResolvePath(string absolutePath)
            {
                // nope :(
                if (!absolutePath.StartsWith(_AbsolutePath))
                    return null;

                // get the rest of the path
                string restOfThePath = absolutePath.Substring(_AbsolutePath.Length);
                int separator = restOfThePath.IndexOf(Path.DirectorySeparatorChar);
                if (separator == -1)
                    return this; // no more sub dirs

                string dirName = restOfThePath.Substring(0, separator);

                // find the sub dir
                foreach (var dir in _Dirs)
                    if (dir._Name == dirName)
                        return dir.ResolvePath(absolutePath);

                // create new directory
                var dirAbsolutePath = _AbsolutePath + dirName + "\\";
                var newDir = new DirectoryInfo(dirAbsolutePath, dirName, this);
                _Dirs.Add(newDir);
                _Dirs.OrderBy(o => o.Name);
                return newDir;                
            }

            public FileInfo AddFile( Red.GameData.Raw.DataFile file, bool isNew )
            {
                if ( !file.Path.StartsWith(_AbsolutePath) )
                    return null;

                // find existing file entry
                string fileName = file.Path.Substring(_AbsolutePath.Length);
                foreach ( var currentFile in _Files )
                {
                    if ( currentFile.File == file )
                        return currentFile;
                }

                // create new file entry
                FileInfo newFile = new FileInfo(file, fileName, this, isNew);
                _Files.Add(newFile);
                _Files.OrderBy(o => o.Name);
                return newFile;
            }

            private readonly string _Name;
            private readonly string _AbsolutePath; // ended with \
            private readonly DirectoryInfo _Parent;
            private readonly List<DirectoryInfo> _Dirs;
            private readonly List<FileInfo> _Files;

            public string Name { get { return _Name; } }
            public string AbsolutePath { get { return _AbsolutePath; } }
            public DirectoryInfo Parent { get { return _Parent; } }
            public List<DirectoryInfo> Dirs { get { return _Dirs; } }
            public List<FileInfo> Files { get { return _Files; } }
        }

        private DirectoryInfo _RootDir;
        private Red.GameData.Raw.DataCollection _RootData;

        private bool BuildFileTree(Red.GameData.Raw.DataCollection data, string rootDataPath)
        {
            // setup root path
            if ( !rootDataPath.EndsWith("\\") )
                rootDataPath += "\\";

            // create root directory
            _RootDir = new DirectoryInfo(rootDataPath, "Depot", null);

            // add files
            foreach (var file in data.AllFiles)
            {
                if (file.Path == "<unassigned>")
                    continue;

                var dir = _RootDir.ResolvePath(file.Path);
                if (dir == null)
                {
                    ErrorBox.ShowError("File '" + file.Path + "' is not under data depot '" + rootDataPath + "'");
                    return false;
                }

                dir.AddFile(file, /*is new*/ false);
            }

            return true;
        }

        class TreeNode_Base : TreeNode
        {
            public TreeNode_Base(string name)
                : base(name)
            { }

            public virtual void UpdateIcon() {}
        }

        class TreeNode_Directory : TreeNode_Base
        {
            private readonly DirectoryInfo _Dir;

            public TreeNode_Directory(DirectoryInfo info)
                : base(info.Name)
            {
                _Dir = info;

                UpdateIcon();

                // subdirectories
                foreach (var dir in _Dir.Dirs)
                    Nodes.Add(new TreeNode_Directory(dir));

                // files
                foreach (var file in _Dir.Files)
                    Nodes.Add(new TreeNode_File(file));
            }

            public void ExpandDirs()
            {
                Expand();

                foreach (var dirNode in Nodes.OfType < TreeNode_Directory >())
                    dirNode.ExpandDirs();
            }

            public override void UpdateIcon()
            {
                if (_Dir.Parent == null)
                {
                    ImageIndex = 0; // depot
                    SelectedImageIndex = 0; // depot
                }
                else
                {
                    ImageIndex = IsExpanded ? 2 : 1;
                    SelectedImageIndex = IsExpanded ? 2 : 1;
                }
            }
        }

        class TreeNode_File : TreeNode_Base
        {
            public readonly FileInfo _File;

            public TreeNode_File(FileInfo info)
                : base(info.Name)
            {
                _File = info;
                UpdateCaption();

                if (_File.IsNew)
                {
                    ImageIndex = 4; // new file
                    SelectedImageIndex = 4;  // new file
                }
                else
                {
                    ImageIndex = 3; // file
                    SelectedImageIndex = 3;  // file
                }
            }

            public void UpdateCaption()
            {
                Text = GetCaption();
            }

            private string GetCaption()
            {
                return _File.Name + "  [ " + _File.File.ObjectsInFile.Count + " object(s) ]";
            }
        }

        // create object hierarchy
        class ObjectElement
        {
            public readonly Red.GameData.Raw.DataCollection _RootData;
            public readonly Red.GameData.Raw.ReflectionData _RootReflection;

            public readonly Red.GameData.Raw.DataObject     _Object;
            public readonly ObjectElement                   _Parent; // object parent
            public readonly List<ObjectElement>             _Children;
            public readonly string                          _Name; // only the root one
            public readonly string                          _ClassName;

            public UInt32                                   _TotalCount;

            public ObjectElement(ref HashSet<Red.GameData.Raw.DataObject> visitedObjects, Red.GameData.Raw.DataCollection rootData, Red.GameData.Raw.ReflectionData rootReflection, Red.GameData.Raw.DataObjectView root)
            {
                _Object = root.RawData;
                _Parent = null;
                _Children = new List<ObjectElement>();
                _Name = root.Name;
                _ClassName = root.ClassName;
                _RootData = rootData;
                _RootReflection = rootReflection;
                _TotalCount = 1;

                visitedObjects.Add(_Object);
                ScanChildren(ref visitedObjects);
            }

            public ObjectElement(ref HashSet<Red.GameData.Raw.DataObject> visitedObjects, ObjectElement parent, Red.GameData.Raw.DataObject obj)
            {
                _Object = obj;
                _Parent = parent;
                _Children = new List<ObjectElement>();
                _Name = "";
                _ClassName = obj.ClassName;
                _RootData = parent._RootData;
                _RootReflection = parent._RootReflection;

                // count hierarchy size
                var par = parent;
                while (par != null)
                {
                    par._TotalCount += 1;
                    par = par._Parent;
                }

                visitedObjects.Add(_Object);
                ScanChildren(ref visitedObjects);
            }

            public void AssignFile(Red.GameData.Raw.DataFile file)
            {
                _Object.MoveToFile(file);

                foreach (var child in _Children)
                    child.AssignFile(file);
            }

            private void ScanChildren(ref HashSet<Red.GameData.Raw.DataObject> visitedObjects)
            {
                foreach ( var prop in _Object.Properties )
                {
                    foreach ( var value in prop.Values )
                    {
                        Guid objectId = Guid.Empty;
                        if ( Red.GameData.Raw.Utils.ParseGuid( value.Value, ref objectId ) )
                        {
                            var childObject = _RootData.Find( objectId );
                            if ( childObject != null && childObject.ParentID == _Object.ID )
                            {
                                if ( !visitedObjects.Contains( childObject ) )
                                {
                                    // create sub-child
                                    var subElem = new ObjectElement( ref visitedObjects, this, childObject );
                                    _Children.Add(subElem);
                                }
                            }
                        }
                    }
                }
            }
        }

        class TreeNode_Object : TreeNode
        {
            public readonly ObjectElement _Element;

            public TreeNode_Object(ObjectElement elem)
                : base("")
            {
                _Element = elem;

                if ( elem._Parent != null )
                {
                    ImageIndex = 6; // normal object
                    SelectedImageIndex = 6; // normal object

                    Text = _Element._ClassName + " " + Red.GameData.Raw.Utils.FormatGuid( _Element._Object.ID );
                }
                else
                {
                    ImageIndex = 5; // root object
                    SelectedImageIndex = 5; // root object

                    Text = _Element._ClassName + " '" + _Element._Name + "'   " + _Element._TotalCount + " object(s)";
                }

                foreach (var subElem in elem._Children)
                {
                    Nodes.Add(new TreeNode_Object(subElem));
                }
            }

            public bool CanDrag()
            {
                return (_Element._Parent == null);
            }

            public virtual void UpdateIcon() { }
        }


        private void RefreshFileTreeView()
        {
            treeView2.BeginUpdate();
            treeView2.Nodes.Clear();

            var rootNode = new TreeNode_Directory(_RootDir);
            rootNode.ExpandDirs();
            treeView2.Nodes.Add(rootNode);

            treeView2.EndUpdate();
            treeView2.Refresh();
        }

            
        public bool AssignObjectsToFiles(Red.GameData.EditorEnvironment env, string rootDataPath)
        {
            // bind
            _RootData = env.Data;

            // any unassigned object ? if there's non we have nothing to do :)
            var unassignedObjects = this._RootData.UnassignedObjects;
            if (unassignedObjects.Count == 0)
                return true;

            // fill in file system
            if (!BuildFileTree(env.Data, rootDataPath))
                return true;

            // refresh the file tree
            RefreshFileTreeView();

            // build object tree
            BuildObjectTree(env, unassignedObjects);

            // show the dialog
            if (ShowDialog() == DialogResult.Cancel)
                return false;

            return true;
        }

        private string _LastDirUsed;

        private void button2_Click(object sender, EventArgs e)
        {
            // get the file
            saveFileDialog1.InitialDirectory = (_LastDirUsed == "") ? _RootDir.AbsolutePath : _LastDirUsed;
            if (saveFileDialog1.ShowDialog() != DialogResult.OK)
                return;

            // get the path to the file
            string filePath = saveFileDialog1.FileName;
            if (!filePath.StartsWith(_RootDir.AbsolutePath))
            {
                ErrorBox.ShowError("File '" + filePath + "' is not placed under root directory '" + _RootDir.AbsolutePath + "'");
                return;
            }

            // create target directory
            var resolvedDir = _RootDir.ResolvePath(filePath);
            if (resolvedDir == null)
            {
                ErrorBox.ShowError("Unable to map directory '" + filePath + "' to depot structure");
                return;
            }

            // use the same dir next time
            _LastDirUsed = resolvedDir.AbsolutePath;

            // create file
            var log = new Form1.LameLogReporter();
            var newFile = _RootData.Action_CreateFile(log, filePath);
            if ( newFile == null )
            {
                ErrorBox.ShowError("Unable to create file '" + filePath + "' in data collection");
                return;
            }

            // create target file
            var resovledFile = resolvedDir.AddFile(newFile, true);
            if (resovledFile == null)
            {
                ErrorBox.ShowError("Unable to create file '" + filePath + "' in depot structure");
                return;
            }

            // refresh the file tree
            RefreshFileTreeView();
        }

        private void BuildObjectTree(Red.GameData.EditorEnvironment env, List<Red.GameData.Raw.DataObject> unassignedObjects)
        {
            treeView1.BeginUpdate();
            treeView1.Nodes.Clear();

            // create the object hierarchy, preserve roots for adding into the tree
            var visitedObjects = new HashSet<Red.GameData.Raw.DataObject>();
            var unasignedRoots = env.Data.ExtractNamedUnassignedObjectList(env.Reflection);
            foreach (var obj in unasignedRoots)
            {
                var elem = new ObjectElement(ref visitedObjects, env.Data, env.Reflection, obj);
                treeView1.Nodes.Add(new TreeNode_Object(elem));
            }

            treeView2.EndUpdate();
            treeView2.Refresh();
        }

        //------------------------

        class DragedObject
        {
            public ObjectElement _Elements;
            public TreeView _SourceTreeView;
            public TreeNode _SourceTreeNode;
        }

        private void treeView1_ItemDrag(object sender, ItemDragEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
               
                var item = e.Item as TreeNode_Object;
                if (item != null && (item._Element._Parent == null) )
                {
                    var data = new DragedObject();
                    data._Elements = item._Element;
                    data._SourceTreeNode = item;
                    data._SourceTreeView = treeView1;

                    DoDragDrop( data, DragDropEffects.Move);
                }
            }
        }

        private void treeView2_ItemDrag(object sender, ItemDragEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
               
                var item = e.Item as TreeNode_Object;
                if (item != null && (item._Element._Parent == null) )
                {
                    var data = new DragedObject();
                    data._Elements = item._Element;
                    data._SourceTreeNode = item;
                    data._SourceTreeView = treeView2;

                    DoDragDrop(data, DragDropEffects.Move);
                }
            }
        }


        private DragedObject GetDraggedObjectData(IDataObject obj)
        {
            try
            {
                return (DragedObject)obj.GetData(typeof(DragedObject));
            }
            catch (Exception) { };

            return null;
        }

        private void treeView2_DragDrop(object sender, DragEventArgs e)
        {
            try
            {
                var data = GetDraggedObjectData(e.Data);
                if ( data != null )
                {
                    // Retrieve the client coordinates of the drop location.
                    Point targetPoint = treeView2.PointToClient(new Point(e.X, e.Y));
                    var targetFileNode = treeView2.GetNodeAt(targetPoint) as TreeNode_File;
                    if (targetFileNode != null)
                    {
                        // assign objects to files
                        data._Elements.AssignFile(targetFileNode._File.File);

                        // update caption on the source node to (if it was a file node)
                        var sourceFileNode = data._SourceTreeNode.Parent as TreeNode_File;
                        if (sourceFileNode != null)
                            sourceFileNode.UpdateCaption();

                        // remove from original tree view
                        if (data._SourceTreeNode.Parent != null)
                            data._SourceTreeNode.Parent.Nodes.Remove(data._SourceTreeNode);
                        else
                            data._SourceTreeView.Nodes.Remove(data._SourceTreeNode);

                        // add to current node
                        targetFileNode.Nodes.Add(new TreeNode_Object(data._Elements));
                        targetFileNode.Expand();
                        targetFileNode.UpdateCaption();            
                    }
                }
            }
            catch (Exception)
            {
            }
        }

        private void treeView2_DragEnter(object sender, DragEventArgs e)
        {
        }

        private void treeView2_DragLeave(object sender, EventArgs e)
        {

        }

        private void treeView2_DragOver(object sender, DragEventArgs e)
        {
            Point targetPoint = treeView2.PointToClient(new Point(e.X, e.Y));
            treeView2.SelectedNode = treeView2.GetNodeAt(targetPoint);

            // only allow rooted classes
            var data = GetDraggedObjectData(e.Data);
            if (data != null)
            {
                var targetFileNode = treeView2.GetNodeAt(targetPoint) as TreeNode_File;
                if (targetFileNode != null)
                {
                    e.Effect = DragDropEffects.Move;
                    return;
                }
            }

            e.Effect = DragDropEffects.None;
        }
    }
}
